import * as THREE from 'three'
import * as TWEEN from '@tweenjs/tween.js'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

/**
 * @typedef {Object} Box3Info
 * @property {THREE.Vector3} size 物体的尺寸
 * @property {THREE.Vector3} center 物体的中心点
 * @property {THREE.Vector3} min 包围盒的最小点
 * @property {THREE.Vector3} max 包围盒的最大点
 * @property {THREE.Vector3} worldPosition 物体的世界坐标
 * @property {THREE.Vector3} worldDirection 物体的世界方向
 * @property {THREE.Quaternion} worldQuaternion 物体的世界旋转
 * @property {THREE.Vector3} worldScale 物体的世界缩放
 * @property {THREE.Box3} box3 包围盒对象
 * @property {number} scale 物体的最大缩放值
 * @property {Function} getSize 获取物体尺寸的函数
 */
/**
 *获取threejs对象的包围盒信息
 * @param {THREE.Mesh} object3D threejs的对象
 * @returns {Box3Info} 包围盒信息
 */
export const getBox3Info = object3D => {
  const box3 = new THREE.Box3()
  box3.setFromObject(object3D)
  const size = new THREE.Vector3()
  const center = new THREE.Vector3()
  box3.getCenter(center)
  box3.getSize(size)

  const worldQuaternion = new THREE.Quaternion()
  const worldPosition = new THREE.Vector3()
  const worldDirection = new THREE.Vector3()
  const worldScale = new THREE.Vector3()
  object3D.getWorldQuaternion(worldQuaternion)
  object3D.getWorldPosition(worldPosition)
  object3D.getWorldDirection(worldDirection)
  object3D.getWorldScale(worldScale)

  const scale = Math.max(worldScale.x, worldScale.y, worldScale.z)

  const getSize = () => box3.getSize(size)

  return {
    size,
    center,
    min: box3.min,
    max: box3.max,
    worldPosition,
    worldDirection,
    worldQuaternion,
    worldScale,
    box3,
    scale,
    getSize,
  }
}

/**
 * 移动相机到目标面前一段距离正对目标
 * @param {THREE.Camera} camera 相机
 * @param {THREE.Object3D} target 目标
 * @param {OrbitControls} cameraControls 相机控制器
 * @returns {TWEEN.Tween} TWEEN动画对象
 */
export const cameraToTarget = (camera, target, cameraControls) => {
  const { size, center, worldDirection: direction } = getBox3Info(target)
  // 相机正对目标,距离一定距离
  const maxDim = Math.max(size.x, size.y, size.z)
  const distance = maxDim
  return new TWEEN.Tween(camera.position)
    .to(
      {
        x: center.x + direction.x * distance,
        y: center.y + direction.y * distance,
        z: center.z + direction.z * distance,
      },
      2000
    )
    .easing(TWEEN.Easing.Exponential.Out)
    .onUpdate(() => {
      camera.lookAt(center)
      cameraControls.target.copy(center)
    })
    .start()
}

/**
 * 移动相机到指定位置并看向指定方向
 * @param {THREE.Camera} camera 相机
 * @param {THREE.Vector3} toPositon 位置
 * @param {THREE.Vector3} lookAt 看向的位置
 * @param {THREE.TrackballControls} cameraControls 相机控制器
 * @returns {TWEEN.Tween[]} TWEEN动画对象
 */
export const cameraToPosition = (camera, toPositon, lookAt, cameraControls) => {
  const tweenA = new TWEEN.Tween(camera.position)
    .to(
      {
        x: toPositon.x,
        y: toPositon.y,
        z: toPositon.z,
      },
      2000
    )
    .easing(TWEEN.Easing.Exponential.Out)
    .onUpdate(() => {
      camera.lookAt(lookAt)
      cameraControls.target.copy(lookAt)
    })
    .start()
  const tweenB = new TWEEN.Tween(camera.up)
    .to(
      {
        x: 0,
        y: 1,
        z: 0,
      },
      2000
    )
    .easing(TWEEN.Easing.Exponential.Out)
    .start()

  return [tweenA, tweenB]
}

/**
 * 为选中物体创建半透明外壳
 * @param {THREE.Object3D} object 物体
 * @param {Object} options 配置信息
 * @param {string} options.color 颜色
 * @param {string} options.lineColor 边线颜色
 * @param {string} options.type 外壳组userData.type的值
 * @returns {THREE.Group} 包含外壳和外壳边线的对象组
 */
export const createHighlightBox = (
  object,
  { color = '#74c7cd', lineColor = '#74c7cd', type = 'highlightBox' } = {}
) => {
  // const { size, center } = getBox3Info(object)
  const geometry = object.geometry.clone()
  const materia = new THREE.MeshBasicMaterial({
    color: new THREE.Color(color),
    transparent: true,
    opacity: 0.8,
    blending: THREE.AdditiveBlending,
  })
  const highlightBox = new THREE.Mesh(geometry, materia)
  highlightBox.scale.multiplyScalar(1.01)
  const geometryEdge = new THREE.EdgesGeometry(geometry)
  const lineMaterial = new THREE.LineBasicMaterial({
    color: new THREE.Color(lineColor),
    linewidth: 10,
  })
  const highlightBoxEdge = new THREE.LineSegments(geometryEdge, lineMaterial)
  const group = new THREE.Group()
  group.add(highlightBox, highlightBoxEdge)
  group.userData.type = type
  return group
}
/**
 * 移除半透明外壳
 * @param {THREE.Group} highlightBox  包围外壳和外壳边线的对象组
 */
export const removeHighlightBox = highlightBox => {
  if (!highlightBox) return
  if (typeof highlightBox.removeFromParent === 'function') {
    highlightBox.removeFromParent()
  } else {
    highlightBox.parent.remove(highlightBox)
  }
}

/**
 * 通过emissive给指定物体的材质添加颜色
 * @param {THREE.Object3D} object 物体
 * @param {string} color 颜色, 例如 '#26507a'
 * @returns {THREE.Material[]} 初始材质数组
 */
export const setHighlightColor = (object, color = '#74c7cd') => {
  const initialMaterials = []
  if (Array.isArray(object.material)) {
    object.material = object.material.map(m => {
      initialMaterials.push(m)
      const newMaterial = m.clone()
      newMaterial.emissive = new THREE.Color(color)
      return newMaterial
    })
  } else {
    const m = object.material
    initialMaterials.push(m)
    const newMaterial = m.clone()
    newMaterial.emissive = new THREE.Color(color)
    object.material = newMaterial
  }
  return initialMaterials
}
/**
 * 还原材质的emissive
 * @param {THREE.Object3D} object 物体
 * @param {THREE.Material[]} initialMaterials 初始材质数组
 */
export const resetHighlightColor = (object, initialMaterials) => {
  if (Array.isArray(object.material)) {
    object.material = [...initialMaterials]
  } else {
    object.material = initialMaterials[0]
  }
}

export const layersMap = new Map([
  ['default', 0],
  ['layer-robot', 1],
  ['layer-warning', 2],
  ['layer-camera', 3],
])
/**
 *
 * @param {THREE.Object3D} object  需要设置层级的对象
 * @param {String} type  层级类型, 对应layersMap的key
 */
export const setLayers = (object, type) => {
  const layers = layersMap.get(type)
  if (layers !== void 0) {
    object.userData.layers = layers
    object.layers.set(layers)
  }
}
export const getLayers = type => {
  const layers = layersMap.get(type)
  if (layers !== void 0) {
    return layers
  }
  return 0
}
